/* extensions id */
const EXT_ID = 'cjabiokabamngnbigeeibddnihgllkgg';
/* mouse button id */
const LBUTTON = 0, MBUTTON = 1, RBUTTON = 2;
/* stroke directions */
const LEFT = 'L', RIGHT = 'R', UP = 'U', DOWN = 'D';
/* url of the options page */
const OPTION_URL = chrome.extension.getURL('options.html');
/* id prefix of user defined strokes */
const CUS_PFX = 'userdef#';
/* sadly.. */
const WIN = !!navigator.platform.match(/^win/i);

/* essential functions */
var $ = {
	get: function(id) {
		if ('string' != typeof id) return id;
		return document.getElementById(id);
	},

	create: function(t, p) {
		var n = document.createElement(t);
		for (k in p) n[k] = p[k];
		return n;
	},

	addcls: function(n, c) {
		var nd = $.get(n);
		if (nd && !$.hascls(nd, c))
			nd.className = nd.className.trim() + ' ' + c;
	},

	delcls: function(n, c) {
		var nd = $.get(n);
		if (nd)
			nd.className = nd.className.replace(new RegExp('(^|\\s+)' + c + '(\\s+|$)', 'g'), ' ');
	},

	hascls: function(n, c) {
		var nd = $.get(n);
		if (nd) return !!(nd.className.match(new RegExp('(^|\\s+)' + c + '(\\s+|$)')));
	},

	show: function(n) {
		$.delcls(n, 'hidden');
	},

	hide: function(n) {
		$.addcls(n, 'hidden');
	},

	toggle: function(n) {
		return $.hascls(n, 'hidden') ? $.show(n) : $.hide(n);
	},

	copy: function(o) {
		return JSON.parse(JSON.stringify(o));
	},

	/* and some not-so-essential functions too */
	notify: function(msg, dur) {
		var id = EXT_ID + '-notify', id2 = id + '-old', nd, nd2,
			confirm, cancel;

		if (nd = $.get(id)) {
			if (nd2 = $.get(id2)) {
				nd2.id = '';
				nd2.innerHTML = '';
				document.body.removeChild(nd2);
			}
			nd.id = id2;
		}

		nd = $.create('span', { id: id });
		nd.style.cssText = '\
-webkit-border-top-right-radius:4px;\
background:#d2e1f6;\
border:#b9c7d9 solid;\
border-width:1px 1px 0 0;\
bottom:0;\
color:#696969;\
font:11px/1.5 tahoma,sans-serif;\
left:0;\
margin:0;\
opacity:0;\
padding:0 0 1px 3px;\
position:fixed;\
text-align:left;\
width:' + (window.innerWidth * .328) + 'px;\
z-index:' + new Date().getTime();

		if (isNaN(dur)) {
			/* duration not set, don't disappear till user confirm */
			nd.appendChild($.create('span', { innerHTML: msg }));
			confirm = $.create('button', { textContent: 'Confirm' });
			confirm.style.float = 'right';
			confirm.addEventListener('click', function(ev) {
				$.fade_out(nd, function() {
					try { document.body.removeChild(nd) }
					catch (e) {}
				});
			}, false);
			nd.appendChild(confirm);
			$.fade_in(nd);
		} else {
			nd.innerHTML = msg;
			$.fade_in(nd, function() {
				setTimeout(function() {
					$.fade_out(nd, function() {
						try { document.body.removeChild(nd) }
						catch (e) {}
					});
				}, Math.max(2, dur) * 1000);
			});
		}
		document.body.appendChild(nd);
	},

	fade_in: function(nd, cb) {
		if (!(nd = $.get(nd))) return;
		nd.style.opacity = 0;
		var i = 0, si = setInterval(function() {
			nd.style.opacity = (i += 0.1);
			if (nd.style.opacity >= 1) {
				clearInterval(si);
				if (!!cb) cb.apply();
			}
		}, 10);
		$.show(nd);
	},

	fade_out: function(nd, cb) {
		if (!(nd = $.get(nd))) return;
		nd.style.opacity = 1;
		var i = 1, si = setInterval(function() {
			nd.style.opacity = (i -= 0.1);
			if (nd.style.opacity <= 0) {
				clearInterval(si);
				$.hide(nd);
				if (!!cb) cb.apply();
			}
		}, 10);
	},

	slide_in: function(nd, cb) {
		if (!(nd = $.get(nd))) return;
		with (nd.style) {
			left = -window.innerWidth + 'px';
			width = window.innerWidth + 'px';
		}
		var i = 1, si = setInterval(function() {
			var l = Math.round(-window.innerWidth * (i -= 0.1));
			nd.style.left = (l > 0 ? 0 : l) + 'px';
			if (l >= 0) {
				clearInterval(si);
				if (!!cb) cb.apply();
			}
		}, 10);
		$.show(nd);
	}
}

/* i18n */
var _ = function(msg)
{
	var i = j = 0, args = arguments, c, l = '';

	if (!!window.i18n && !!i18n[msg]) msg = i18n[msg];
	/* % is the placeholder, %% escapes % */
	for (i = 0; i < msg.length; ++i) {
		c = msg.charAt(i);
		if ('%' != c) l += c;
		else if ('%' == c && '%' == msg.charAt(i+1)) { l += c; ++i }
		else l += !!arguments[++j] ? arguments[j] : c;
	}
	return l;
}

String.prototype.trim = function()
{
	return this.replace(/^\s+|\s+$/, '');
}

Array.prototype.unique = function()
{
	var o = {}, i, l = this.length;
	for (i = 0; i < l; o[this[i]] = this[i++]);
	this.splice(0, l);
	for(i in o) this.push(o[i]);
}
